<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>语音流式处理测试</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            background: white;
            padding: 20px;
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        .button {
            background: #007bff;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 5px;
            cursor: pointer;
            margin: 5px;
        }
        .button:hover {
            background: #0056b3;
        }
        .button:disabled {
            background: #ccc;
            cursor: not-allowed;
        }
        .recording {
            background: #dc3545 !important;
        }
        .status {
            margin: 10px 0;
            padding: 10px;
            border-radius: 5px;
            background: #e9ecef;
        }
        .progress-bar {
            width: 100%;
            height: 20px;
            background: #e9ecef;
            border-radius: 10px;
            overflow: hidden;
            margin: 10px 0;
        }
        .progress-fill {
            height: 100%;
            background: #28a745;
            width: 0%;
            transition: width 0.3s ease;
        }
        .log {
            background: #f8f9fa;
            border: 1px solid #dee2e6;
            border-radius: 5px;
            padding: 10px;
            height: 200px;
            overflow-y: auto;
            font-family: monospace;
            font-size: 12px;
        }
        .step {
            display: inline-block;
            margin: 5px;
            padding: 5px 10px;
            border-radius: 15px;
            background: #e9ecef;
            color: #6c757d;
        }
        .step.active {
            background: #007bff;
            color: white;
        }
        .step.completed {
            background: #28a745;
            color: white;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>语音流式处理测试</h1>
        
        <div>
            <button id="start-record" class="button">🎤 开始录音</button>
            <button id="stop-record" class="button" disabled>⏹️ 停止录音</button>
            <button id="play-audio" class="button" disabled>▶️ 播放</button>
        </div>
        
        <div class="status" id="status">
            准备就绪
        </div>
        
        <div class="progress-bar">
            <div class="progress-fill" id="progress-fill"></div>
        </div>
        <div id="progress-text">0%</div>
        
        <div id="steps">
            <div class="step" data-step="ASR">🎤 语音识别</div>
            <div class="step" data-step="AI">🤖 AI对话</div>
            <div class="step" data-step="TTS">🔊 语音合成</div>
            <div class="step" data-step="STORAGE">💾 存储完成</div>
        </div>
        
        <div>
            <h3>识别文本:</h3>
            <div id="recognized-text">-</div>
            
            <h3>AI回复:</h3>
            <div id="ai-reply">-</div>
            
            <h3>语音URL:</h3>
            <div id="voice-url">-</div>
        </div>
        
        <div>
            <h3>调试日志:</h3>
            <div class="log" id="log"></div>
        </div>
    </div>

    <script>
        class VoiceStreamTest {
            constructor() {
                this.mediaRecorder = null;
                this.audioChunks = [];
                this.isRecording = false;
                this.currentTaskId = null;
                this.ws = null;
                this.audio = null;
                
                this.init();
            }
            
            init() {
                this.bindEvents();
                this.log('应用初始化完成');
            }
            
            bindEvents() {
                document.getElementById('start-record').addEventListener('click', () => {
                    this.startRecording();
                });
                
                document.getElementById('stop-record').addEventListener('click', () => {
                    this.stopRecording();
                });
                
                document.getElementById('play-audio').addEventListener('click', () => {
                    this.togglePlayback();
                });
            }
            
            async startRecording() {
                try {
                    const stream = await navigator.mediaDevices.getUserMedia({ 
                        audio: {
                            sampleRate: 44100,
                            channelCount: 1,
                            echoCancellation: true,
                            noiseSuppression: true
                        } 
                    });
                    
                    this.mediaRecorder = new MediaRecorder(stream, { 
                        mimeType: 'audio/webm;codecs=opus' 
                    });
                    this.audioChunks = [];
                    
                    this.mediaRecorder.ondataavailable = (event) => {
                        if (event.data.size > 0) {
                            this.audioChunks.push(event.data);
                        }
                    };
                    
                    this.mediaRecorder.onstop = () => {
                        this.audioBlob = new Blob(this.audioChunks, { type: 'audio/webm' });
                        this.handleRecordingComplete(this.audioBlob);
                        stream.getTracks().forEach(track => track.stop());
                    };
                    
                    this.mediaRecorder.onerror = (event) => {
                        this.log('录音错误: ' + event.error);
                    };
                    
                    this.mediaRecorder.start(100);
                    this.isRecording = true;
                    this.updateRecordButton(true);
                    this.log('开始录音...');
                    
                } catch (error) {
                    this.log('录音失败: ' + error.message);
                }
            }
            
            stopRecording() {
                if (this.mediaRecorder && this.isRecording) {
                    this.mediaRecorder.stop();
                    this.isRecording = false;
                    this.updateRecordButton(false);
                    this.log('录音完成，正在处理...');
                }
            }
            
            async handleRecordingComplete(audioBlob) {
                try {
                    // 将音频转换为Base64
                    const base64Data = await this.blobToBase64(audioBlob);
                    const audioData = `data:audio/webm;base64,${base64Data}`;
                    
                    this.log('音频数据长度: ' + audioData.length + ' 字符');
                    
                    // 创建语音处理任务
                    const response = await fetch('/api/voice/stream-task', {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json',
                        },
                        body: JSON.stringify({
                            audioData: audioData,
                            format: 'webm',
                            language: 'zh',
                            characterId: 1,
                            userId: 1,
                            conversationId: 1,
                            enableEmotionDetection: false
                        })
                    });
                    
                    const result = await response.json();
                    this.log('API响应: ' + JSON.stringify(result, null, 2));
                    
                    if (result.code === 200) {
                        this.currentTaskId = result.data.taskId;
                        this.log('任务创建成功: ' + this.currentTaskId);
                        this.connectWebSocket();
                    } else {
                        this.log('任务创建失败: ' + result.message);
                    }
                    
                } catch (error) {
                    this.log('处理录音失败: ' + error.message);
                }
            }
            
            connectWebSocket() {
                const wsUrl = `ws://localhost:8080/ws/voice-task/${this.currentTaskId}`;
                this.log('连接WebSocket: ' + wsUrl);
                
                this.ws = new WebSocket(wsUrl);
                
                this.ws.onopen = () => {
                    this.log('WebSocket连接已建立');
                };
                
                this.ws.onmessage = (event) => {
                    try {
                        const message = JSON.parse(event.data);
                        this.log('收到WebSocket消息: ' + JSON.stringify(message, null, 2));
                        this.handleWebSocketMessage(message);
                    } catch (error) {
                        this.log('解析WebSocket消息失败: ' + error.message);
                    }
                };
                
                this.ws.onclose = (event) => {
                    this.log('WebSocket连接已关闭: ' + event.code + ' ' + event.reason);
                };
                
                this.ws.onerror = (error) => {
                    this.log('WebSocket错误: ' + error);
                };
            }
            
            handleWebSocketMessage(message) {
                switch (message.type) {
                    case 'PROGRESS_UPDATE':
                        this.updateProgress(message.data);
                        break;
                    case 'TASK_COMPLETED':
                        this.handleTaskCompleted(message.data);
                        break;
                    case 'TASK_FAILED':
                        this.handleTaskFailed(message.data);
                        break;
                    default:
                        this.log('未知消息类型: ' + message.type);
                }
            }
            
            updateProgress(data) {
                this.log(`进度更新: ${data.progress}% - ${data.currentStep} - ${data.message}`);
                
                // 更新进度条
                document.getElementById('progress-fill').style.width = data.progress + '%';
                document.getElementById('progress-text').textContent = data.progress + '%';
                
                // 更新步骤指示器
                document.querySelectorAll('.step').forEach(step => {
                    step.classList.remove('active', 'completed');
                    const stepName = step.dataset.step;
                    
                    if (stepName === data.currentStep) {
                        step.classList.add('active');
                    } else if (this.isStepCompleted(stepName, data.currentStep)) {
                        step.classList.add('completed');
                    }
                });
                
                // 更新状态
                document.getElementById('status').textContent = data.message || '处理中...';
                
                // 更新文本内容
                if (data.recognizedText) {
                    document.getElementById('recognized-text').textContent = data.recognizedText;
                }
                if (data.aiReply) {
                    document.getElementById('ai-reply').textContent = data.aiReply;
                }
                if (data.voiceUrl) {
                    document.getElementById('voice-url').textContent = data.voiceUrl;
                }
            }
            
            handleTaskCompleted(data) {
                this.log('任务完成!');
                this.updateProgress(data);
                document.getElementById('status').textContent = '处理完成！';
                
                // 加载音频
                if (data.voiceUrl) {
                    this.audio = new Audio(data.voiceUrl);
                    document.getElementById('play-audio').disabled = false;
                }
                
                // 断开WebSocket
                if (this.ws) {
                    this.ws.close();
                }
            }
            
            handleTaskFailed(data) {
                this.log('任务失败: ' + data.errorMessage);
                document.getElementById('status').textContent = '处理失败: ' + data.errorMessage;
                
                // 断开WebSocket
                if (this.ws) {
                    this.ws.close();
                }
            }
            
            togglePlayback() {
                if (this.audio) {
                    if (this.audio.paused) {
                        this.audio.play();
                        document.getElementById('play-audio').textContent = '⏸️ 暂停';
                    } else {
                        this.audio.pause();
                        document.getElementById('play-audio').textContent = '▶️ 播放';
                    }
                }
            }
            
            isStepCompleted(stepName, currentStep) {
                const stepOrder = ['ASR', 'AI', 'TTS', 'STORAGE'];
                const currentIndex = stepOrder.indexOf(currentStep);
                const stepIndex = stepOrder.indexOf(stepName);
                return stepIndex < currentIndex;
            }
            
            updateRecordButton(isRecording) {
                const startBtn = document.getElementById('start-record');
                const stopBtn = document.getElementById('stop-record');
                
                startBtn.disabled = isRecording;
                stopBtn.disabled = !isRecording;
                
                if (isRecording) {
                    startBtn.classList.add('recording');
                } else {
                    startBtn.classList.remove('recording');
                }
            }
            
            async blobToBase64(blob) {
                return new Promise((resolve, reject) => {
                    const reader = new FileReader();
                    reader.onload = () => resolve(reader.result.split(',')[1]);
                    reader.onerror = reject;
                    reader.readAsDataURL(blob);
                });
            }
            
            log(message) {
                const logDiv = document.getElementById('log');
                const timestamp = new Date().toLocaleTimeString();
                logDiv.innerHTML += `[${timestamp}] ${message}\n`;
                logDiv.scrollTop = logDiv.scrollHeight;
                console.log(message);
            }
        }
        
        // 初始化应用
        document.addEventListener('DOMContentLoaded', () => {
            new VoiceStreamTest();
        });
    </script>
</body>
</html>
